
/**
 * Copyright 2009 cestum.org
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
 
package org.waveprotocol.wave.model.document.operation.impl {
import org.goverla.collections.ArrayList;
import org.goverla.collections.HashMap;
import org.goverla.collections.ListCollectionViewIterator;
import org.waveprotocol.wave.model.document.operation.AnnotationBoundaryMap;
import org.waveprotocol.wave.model.document.operation.util.AttributeUpdate;
import org.waveprotocol.wave.model.document.operation.util.ImmutableAnnotationsUpdateMap;
import org.waveprotocol.wave.model.util.Preconditions;

public class AnnotationsUpdateImpl extends ImmutableAnnotationsUpdateMap implements AnnotationsUpdate {

  public static var EMPTY_MAP:AnnotationsUpdateImpl= new AnnotationsUpdateImpl(null);

  public function AnnotationsUpdateImpl(updates:ArrayList = null) {
    super(updates);
  }

  
  override protected function createFromList(updates:ArrayList):AnnotationsUpdate {
    return new AnnotationsUpdateImpl(updates);
  }

  
  public function composeWithAnnotationBoundary1(map:AnnotationBoundaryMap):AnnotationsUpdate {
	var newUpdates:HashMap= new HashMap();
	var itr:ListCollectionViewIterator = updates.createIterator();
	while(itr.hasNext()) {
		var u:AttributeUpdate  = itr.next() as AttributeUpdate;
		newUpdates.addItem(u.name, u);
	}
    for (var i:int= 0; i < map.changeSize(); i++) {
      var key:String= map.getChangeKey(i);
      newUpdates.addItem(key, new AttributeUpdate(key, map.getOldValue(i), map.getNewValue(i)));
    }
    for (var i1:int= 0; i1 < map.endSize(); i1++) {
      newUpdates.removeItemAt(map.getEndKey(i1));
    }
    var l:ArrayList= new ArrayList(newUpdates.values);
	//FIXME
    //Collections.sort(l, comparator);
    return createFromList(l);
  }
  
  /**
   * A string that is larger (according to compareTo) than any valid annotation key.
   */
  private static var MAX_STRING:String= String.fromCharCode(65534);
  
  public function composeWithAnnotationBoundary(map:AnnotationBoundaryMap):AnnotationsUpdate {
	  var newUpdates:ArrayList= new ArrayList();
	  var existingIndex:int = 0;
	  var changeIndex:int = 0;
	  var endIndex:int = 0;
	  while (existingIndex < updates.length
		  || changeIndex < map.changeSize()
		  || endIndex < map.endSize()) {
		  var existingKey:String = existingIndex < updates.length ? updates.getItemAt(existingIndex).name  : MAX_STRING;
		  var changeKey:String = changeIndex < map.changeSize() ? map.getChangeKey(changeIndex)  : MAX_STRING;
		  var endKey:String = endIndex < map.endSize() ? map.getEndKey(endIndex) : MAX_STRING;
		  // cases:
		  // existingKey < endKey && existingKey < changeKey: keep, advance existing
		  // existingKey < endKey && existingKey = changeKey: replace, advance existing & change
		  // existingKey < endKey && existingKey > changeKey: add change, advance change
		  // existingKey = endKey && existingKey < changeKey: remove, advance existing & end
		  // existingKey = endKey && existingKey = changeKey: error (key in both change and end)
		  // existingKey = endKey && existingKey > changeKey: remove, add change, advance all 3
		  // existingKey > endKey: error (attempt to end key that is not part of the update)
		  var existingVsEnd:int = existingKey.localeCompare(endKey);
		  var existingVsChange:int = existingKey.localeCompare(changeKey);
		  if (existingVsEnd < 0) {
			  if (existingVsChange < 0) {
				  newUpdates.addItem(updates.getItemAt(existingIndex));
				  existingIndex++;
			  } else if (existingVsChange == 0) {
				  newUpdates.addItem(new AttributeUpdate(changeKey,
					  map.getOldValue(changeIndex),
					  map.getNewValue(changeIndex)));
				  existingIndex++;
				  changeIndex++;
			  } else if (existingVsChange > 0) {
				  newUpdates.addItem(new AttributeUpdate(changeKey,
					  map.getOldValue(changeIndex),
					  map.getNewValue(changeIndex)));
				  changeIndex++;
			  } else {
				 // assert false;
			  }
		  } else if (existingVsEnd == 0) {
			  if (existingVsChange < 0) {
				  existingIndex++;
				  endIndex++;
			  } else if (existingVsChange == 0) {
				  Preconditions.illegalArgument("AnnotationBoundaryMap with key both in change and end: "
					  + changeKey);
			  } else if (existingVsChange > 0) {
				  newUpdates.addItem(new AttributeUpdate(changeKey,
					  map.getOldValue(changeIndex),
					  map.getNewValue(changeIndex)));
				  existingIndex++;
				  endIndex++;
				  changeIndex++;
			  } else {
				  //assert false;
			  }
		  } else if (existingVsEnd > 0) {
			  Preconditions.illegalArgument("Attempt to end key that is not part of the update: "
				  + endKey);
		  } else {
			 // assert false;
		  }
	  }
	  return createFromList(newUpdates);
  }
  

  public function containsKey(key:String):Boolean {
    Preconditions.checkNotNull(key, "Null key");
	var itr:ListCollectionViewIterator = updates.createIterator();
	while(itr.hasNext()) {
		var u:AttributeUpdate  = itr.next() as AttributeUpdate;
		if (key == u.name) {
			return true;
		}
	}
    return false;
  }


 override  public function composeWith(mutation:AnnotationsUpdate):AnnotationsUpdate {	
	return AnnotationsUpdate(super.composeWith(mutation));
 }
}
}